﻿using System.Collections.Generic;
using System.Linq;
using TSU.FAMC.ComputerEngineering.Classes.Enums;

namespace TSU.FAMC.ComputerEngineering.Classes.Bdds.Basis
{
    /// <summary>
    /// Diagram node.
    /// </summary>
    public class Node
    {
        /// <summary>
        /// Outgoing edges for child nodes.
        /// </summary>
        public List<Edge> Children = new List<Edge>();

        /// <summary>
        /// Ingoing edges from parent nodes.
        /// </summary>
        public List<Edge> Parents = new List<Edge>();

        /// <summary>
        /// Node id.
        /// </summary>
        public int Id;

        /// <summary>
        /// Node label
        /// </summary>
        public string Label { get; set; }

        /// <summary>
        /// Indicates whether node is terminal-0 node.
        /// </summary>
        public bool IsTerminal0
        {
            get
            {
                return Children.Count == 0 && Label == "0";
            }
        }

        /// <summary>
        /// Indicates whether node is terminal-0 node.
        /// </summary>
        public bool IsTerminal1
        {
            get
            {
                return Children.Count == 0 && Label == "1";
            }
        }

        /// <summary>
        /// Links two nodes via parent-child relation.
        /// </summary>
        /// <param name="childNode">Child node</param>
        /// <param name="edgeType">Type of the edge, linking two nodes.</param>
        /// <returns>Created edge for linkage.</returns>
        public Edge LinkChild(Node childNode, BddEdgeType edgeType = BddEdgeType.True)
        {
            var edge = new Edge { Parent = this, Child = childNode, Type = edgeType};

            Children.Add(edge);
            childNode.Parents.Add(edge);

            return edge;
        }

        /// <summary>
        /// Deletes link between two nodes.
        /// </summary>
        /// <param name="node">Node object to delete link from/to.</param>
        public void UnlinkNode(Node node)
        {
            // checking children
            var edgesToRemove = new List<Edge>();
            foreach (var edge in Children.Where(edge => ReferenceEquals(edge.Child, node)))
            {
                // memorizing edge
                edgesToRemove.Add(edge);

                // updating child node
                var childNode = edge.Child;
                childNode.Parents.Remove(edge);

                // clearing edge
                edge.Child = null;
                edge.Parent = null;
            }

            // clearing references to edge
            foreach (var edge in edgesToRemove)
            {
                Children.Remove(edge);   
            }


            // checking children
            edgesToRemove.Clear();
            foreach (var edge in Parents.Where(edge => ReferenceEquals(edge.Parent, node)))
            {
                // memorizing edge
                edgesToRemove.Add(edge);

                // updating child node
                var parentNode = edge.Parent;
                parentNode.Children.Remove(edge);

                // clearing edge
                edge.Child = null;
                edge.Parent = null;
            }

            // clearing references to edge
            foreach (var edge in edgesToRemove)
            {
                Parents.Remove(edge);
            }
        }

        /// <summary>
        /// Merges two nodes and it's sub-bdds by deleting second sub-bdd and moving second ingoing edge into first node.
        /// </summary>
        /// <param name="node">Node to merge.</param>
        public void Consume(Node node)
        {
            // relinking node from parents
            var consumedParents = node.Parents.Select(edge => edge.Parent).ToList();
            foreach (var parent in consumedParents)
            {
                node.UnlinkNode(parent);
                parent.LinkChild(this);
            }

            // destroing node relations
            node.Destroy();
        }

        /// <summary>
        /// Destroys all relations between node and all parent nodes in all levels.
        /// </summary>
        public void Destroy()
        {
            DoDestroy(this);
        }

        /// <summary>
        /// Performs node relation destroy from top to bottom recursively.
        /// </summary>
        /// <param name="currentNode">Currently destroing node.</param>
        private static void DoDestroy(Node currentNode)
        {
            var children = currentNode.Children.Select(edge => edge.Child).ToList();
            foreach (var child in children)
            {
                // unlinking child node
                currentNode.UnlinkNode(child);

                // destoroing child node relations
                DoDestroy(child);
            }
        }

        /// <summary>
        /// Removes node but maintain links from parents to children by passing them directly.
        /// </summary>
        public void Collapse()
        {
            var parents = Parents.Select(edge => edge.Parent).ToList();

            // unlinking from parents
            foreach (var parent in parents)
            {
                UnlinkNode(parent);
            }

            var children = Children.Select(edge => edge.Child).ToList();

            // unlinking children
            foreach (var child in children)
            {
                UnlinkNode(child);
            }


            // creating direct links
            foreach (var parent in parents)
            {
                foreach (var child in children)
                {
                    parent.LinkChild(child);
                }
            }
        }

        /// <summary>
        /// Returns first child node linked via False type edge.
        /// </summary>
        /// <returns>False-edge node.</returns>
        public Node Low()
        {
            if (Children.Count == 0)
            {
                return null;
            }

            var edges = Children.Where(edge => edge.Type == BddEdgeType.False).ToList();
            return edges.Count == 0 ? null : edges.Select(edge => edge.Child).First();
        }

        /// <summary>
        /// Returns first child node linked via True type edge.
        /// </summary>
        /// <returns>True-edge node.</returns>
        public Node High()
        {
            if (Children.Count == 0)
            {
                return null;
            }

            var edges = Children.Where(edge => edge.Type == BddEdgeType.True).ToList();
            return edges.Count == 0 ? null : edges.Select(edge => edge.Child).First();
        }

        /// <summary>
        /// Links child node via False edge.
        /// </summary>
        /// <param name="node">Node to link.</param>
        /// <returns>Created False-type edge.</returns>
        public Edge LinkChildLow(Node node)
        {
            return LinkChild(node, BddEdgeType.False);
        }

        /// <summary>
        /// Links child node via True edge.
        /// </summary>
        /// <param name="node">Node to link.</param>
        /// <returns>Created rue-type edge.</returns>
        public Edge LinkChildHigh(Node node)
        {
            return LinkChild(node);
        }
    }
}
